package sg.edu.nus.iss.vmcs.store;

/*
 * Copyright 2011 ISS.
 * The contents contained in this document may not be reproduced in any
 * form or by any means, without the written permission of ISS, other
 * than for the purpose for which it has been supplied.
 *
 */

import java.io.*;

import sg.edu.nus.iss.vmcs.util.VMCSException;

/**
 * This control object manages changes in Cash Store attributes and the Drinks 
 * Store attributes. 

 */

public class StoreController {

	private CashStore cStore;
	private DrinksStore dStore;

	private ItemLoader cashLoader;
	private ItemLoader drinksLoader;


    private static StoreController instance;
    private static Object classLock = StoreController.class;
    public static StoreController getInstance(){
        synchronized (classLock) {
            if(instance == null) {
                instance = new StoreController();
            }
            return instance;
        }
    }
    
	private StoreController() {}

    /**Creates and initializes the two stores (CASH / DRINK).
    *
    * @param cashLoader    PropertyLoader to load cash store information.
    * @param drinksLoader  PropertyLoader to load drink store information.
    * @throws java.io.IOException 
    */
	public void initialize(ItemLoader cashLoader, ItemLoader drinksLoader) throws IOException {
		this.cashLoader = cashLoader;
		this.drinksLoader = drinksLoader;
		initializeStores();
	}

/**
 * This operations initializes the Cash Store and Drinks Store by 
 * calling the InitializeCashStore and InitializeDrinkStore methods 
 * respectively.
 * @throws java.io.IOException
 */
	private void initializeStores() throws IOException {
		initializeCashStore();
		initializeDrinkStore();
	}

/**
 * This operations initializes the Drinks Store by first reading 
 * the property file.  It then sets the store size, and then adds the 
 * drinks store item into the store.
 * @throws java.io.IOException
 */
	private void initializeDrinkStore() throws IOException {
		dStore = new DrinksStore();

		// get the drink file from the environment property file;
		int numOfItems = drinksLoader.getNumOfItems();
		dStore.setStoreSize(numOfItems);

		for (int i = 0; i < numOfItems; i++) {
            DrinksStoreItem item = (DrinksStoreItem) drinksLoader.getItem(i);
			StoreObject brand = item.getContent();
			StoreObject existingBrand = dStore.findObject(brand.getName());
			if (existingBrand != null) {
			    item.setContent(existingBrand);
			}
			dStore.addItem(i, item);
		}
	}

/**
 * This operations initializes the Cash Store by first reading 
 * the property file.  It then sets the store size, and then adds the 
 * Cash store item into the store.
 * @throws java.io.IOException
 */
	private void initializeCashStore() throws IOException {
		cStore = new CashStore();
		// get the cash file from the environment property file;
		int numOfItems = cashLoader.getNumOfItems();
		cStore.setStoreSize(numOfItems);

		for (int i = 0; i < numOfItems; i++) {
		    CashStoreItem item = (CashStoreItem) cashLoader.getItem(i);
			cStore.addItem(i, item);
		}
	}

/**
 * This operation will instruct the Cash Store to store the Coin sent 
 * as input, and the update the display on the Machinery Simulator 
 * Panel. 
 * @param c the coin object that is inserted into the machine. 
 */
	public void storeCoin(Coin c) {
		int idx = cStore.findCashStoreIndex(c);
		CashStoreItem item;
		item = (CashStoreItem) this.getStoreItem(Store.CASH, idx);
		item.increment();
	}

/**
 * Retrieves the store size of either the Cash Store or Drinks Store.
 * @param type the type of store, either Cash Store or Drinks Store.
 * @return returns the size of the store.
 */
	public int getStoreSize(int type) {
		if (type == Store.CASH)
			return cStore.getStoreSize();
		else
			return dStore.getStoreSize();
	}

/**
 * This operation retrieves the Store items in the store.
 * @param type the type of store - either Cash Store or Drinks Store.
 * @return returns an array list of Store items.
 */
	public StoreItem[] getStoreItems(int type) {
		if (type == Store.CASH)
			return cStore.getItems();
		else
			return dStore.getItems();
	}

/**
 * This operation will either:
 * - instruct the Cash Store to update the quantity of a coin 
 * denomination to the new value supplied and update the total cash 
 * held in the Cash Store; or
 * - instruct the Drinks Store to update the drinks stock for a drinks 
 * brand requested to a new value supplied. 

 * @param type the type of Store - either Cash Store or Drinks Store.
 * @param idx the denomination of the coin, or the brand of the drink.
 * @param qty the quantity of the store item that needs to be changed.
 */
	public void changeStoreQty(int type, int idx, int qty) {
			System.out.println(
				"StoreController.changeStoreQty: type:"
					+ type
					+ "   qty:"
					+ qty);
			if (type == Store.CASH)
				cStore.setQuantity(idx, qty);
			else
				dStore.setQuantity(idx, qty);
	}

 /**
  * This operation retrieves either the Cash Store item or the Drinks 
  * Store item.
  * @param type  the type of store - either Cash Store or Drinks Store.
  * @param idx the index of the Cash Store or Drinks Store item.
  * @return returns the Store item - either a Cash Store item or Drinks
  * Store item.
  */
	public StoreItem getStoreItem(int type, int idx) {
		if (type == Store.CASH)
			return cStore.getStoreItem(idx);
		else
			return dStore.getStoreItem(idx);
	}

 /**
  * This operation will be used to set the price of a drink brand as 
  * determined by the Maintainer.  
  * @param idx the index that represents a particular brand of drinks.
  * @param pr the price of a particular drink brand that has to be set.
  */
	public void setPrice(int idx, int pr)  {
		DrinksStoreItem item;

		item = (DrinksStoreItem) dStore.getStoreItem(idx);
		DrinksBrand bd;

		bd = (DrinksBrand) item.getContent();

		bd.setPrice(pr);
	}
        
 /**
  * This operation returns the total cash held in the Cash Store. 
  * @return returns the total cash held in the vending machine.
  */
	public int getTotalCash()   {
		int i;
		int size;

		size = cStore.getStoreSize();
		CashStoreItem item;
		int qty;
		int val;
		int tc = 0;
		Coin c;

		for (i = 0; i < size; i++) {
			item = (CashStoreItem) cStore.getStoreItem(i);
			qty = item.getQuantity();
			c = (Coin) item.getContent();
			val = c.getValue();
			tc = tc + qty * val;
		}
		return tc;
	}

 /**
  * This operation is to facilitate the transfer of all cash in Cash 
  * Store to the maintainer.  
  * @return returns the total quantity of coins.
  */
	public int transferAll()  {
		int i;
		int cc = 0; // coin quantity;
		int size = cStore.getStoreSize();

		CashStoreItem item;
		for (i = 0; i < size; i++) {
			item = (CashStoreItem) cStore.getStoreItem(i);
			cc = cc + item.getQuantity();
			item.setQuantity(0);
		}

		return cc;
	}

 /**
  * This operation will close down the store management function of the 
  * vending machine. This involves saving the attributes of the stores 
  * to the property files by calling the saveCashProperties method and 
  * the saveDrinksProperties method. 
  * @throws java.io.IOException
  */
	public void closeDown() throws IOException {
		// save back cash property;
		saveCashProperties();
        saveDrinksProperties();
	}

 /**
  * This operation saves the attributes of the Cash Store to the 
  * input file. 
  * @throws java.io.IOException
  */
	private void saveCashProperties() throws IOException {
		int size = cStore.getStoreSize();
		cashLoader.setNumOfItems(size);
		for (int i = 0; i < size; i++) {
			cashLoader.setItem(i, cStore.getStoreItem(i));
		}
		cashLoader.save();
	}

/**
 * save the drink property when simulation is ended.
 */
        
/**
 * This operation saves the attributes of the Drinks Store to the 
 * input file. 
 * @throws java.io.IOException
 */
	private void saveDrinksProperties() throws IOException {
		int size = dStore.getStoreSize();
		drinksLoader.setNumOfItems(size);
		for (int i = 0; i < size; i++) {
			drinksLoader.setItem(i, dStore.getStoreItem(i));
		}
		drinksLoader.save();
	}

/**
 * This operation instructs the the Drinks Store to dispense one drink, 
 * and then updates the Machinery Simulator Panel. It returns TRUE or 
 * FALSE to indicate whether dispensing was successful. 
 * @param idx the index that represents a particular drink brand.
 */
	public void dispenseDrink(int idx)  {
		DrinksStoreItem item;
		item = (DrinksStoreItem) getStoreItem(Store.DRINK, idx);
		item.decrement();
	}

/**
 * This operation returns a store of a specified type - 
 * either Cash or Drinks). 
 * @param type the type of store - either Cash Store or Drinks Store
 * @return returns the store of either the Cash or Drinks type.
 */
	public Store getStore(int type) {
		if (type == Store.CASH)
			return (Store) cStore;
		else
			return (Store) dStore;
	}
        
/**
 * This operation instructs the Cash Store to issue a number of coins 
 * of a specific denomination, and then updates the Machinery Simulator 
 * Panel. It returns TRUE or FALSE to indicate whether the change issue 
 * was successful. 
 * @param idx the index that represents the denomination of coin.
 * @param numOfCoins the number of coins of a particular denomination 
 * that has to be returned as change.
 */
	public void giveChange(int idx, int numOfCoins) throws VMCSException {
		CashStoreItem item;
		item = (CashStoreItem) getStoreItem(Store.CASH, idx);
		for (int i = 0; i < numOfCoins; i++){
			item.decrement();
			//FOR MEMENTO TESTING Uncomment this
			//throw new VMCSException("TEST", "GIVE CHANE PROBLEM");
			
		}
		
		
	}
}
